---
title: "4：ルーティング"
---

import SvgRoutingPipelines from './routing-pipelines.svg';

プロキシができる、最も基本的なタスクの一つは、「ルーティング」です。HTTPでのルーティングは通常、リクエスト内の「Host」ヘッダーと、要求されたURIがベースになっています。つまり、プロキシは「abc.com/api/v1/login」のようなことを、リクエストを処理する特定のターゲットホストにマップできる必要があります。

## URLRouter

PipyはURLからあらゆるタイプの値への高速なマッピングクラス、 [_URLRouter_](/reference/api/algo/URLRouter) を提供します。ここでは、ターゲットのアドレスとポートを持つ文字列の値にマップしたいと思います。

このルーターを定義するため、URLRouterオブジェクトを`new`で作成し、ルーティングテーブルを与えます。:

``` js
new algo.URLRouter({
  '/hi/*': 'localhost:8080',
  '/echo': 'localhost:8081',
  '/ip/*': 'localhost:8082',
})
```

## カスタムグローバル変数

このルーターは、スクリプト全体に渡ってアクセスできなければならないので、グローバル変数に入れます。

[パート2](/tutorial/02-echo/#コードの説明-1)では、ビルトインのグローバル変数`__inbound`を扱いました。今回は`_router`というカスタムグローバル変数を定義します。これは、スクリプトの冒頭で [_pipy()_](/reference/api/pipy) に与えたパラメーターを通じて行います。そこで先に行ったものに再び追加します。

``` js
- pipy()
+ pipy({
+   _router: new algo.URLRouter({
+     '/hi/*': 'localhost:8080',
+     '/echo': 'localhost:8081',
+     '/ip/*': 'localhost:8082',
+   }),
+ })

  .listen(8000)
    .demuxHTTP('forward')
```

> カスタム変数は、有効なJavaScript識別子であれば、どんな名前を付けてもかまいません。しかし、慣例として、関数パラメーターで使う変数と区別するために、すべてのグローバル変数には先頭にアンダーラインを一文字付けることをお勧めします。

さらに、グローバルでアクセス性がある _URLRouter_ オブジェクトに加えて、ルーティングテーブルから見つけたターゲットを保存しておくための、第二のグローバル変数が必要です。この名前を`_target`とし、初期値は空の文字列にしておきます。

``` js
  pipy({
    _router: new algo.URLRouter({
      '/hi/*': 'localhost:8080',
      '/echo': 'localhost:8081',
      '/ip/*': 'localhost:8082',
    }),

+   _target: '',
  })
```

## ルーティング

以上で、必要な変数はできました。次は[_URLRouter.find()_](/reference/api/algo/URLRouter/find) をコールして、リクエストの内容から、`_target` の値を求めます。これは、_demuxHTTP_ と _muxHTTP_ の間の _handleMessageStart_ フィルターで行います。 _demuxHTTP_ と _muxHTTP_ は同じパイプラインにないので、新しいサブパイプラインをこの間に挿入します。新しいサブパイプラインの名前は _request_ です。

``` js 
  .listen(8000)
-   .demuxHTTP('forward')
+   .demuxHTTP('request')

+ .pipeline('request')
+   .handleMessageStart(
+     msg => (
+       _target = _router.find(
+         msg.head.headers.host,
+         msg.head.path,
+       )
+     )
+   )
+   .link('forward')

  .pipeline('forward')
    .muxHTTP('connection')
```

ここで、パイプライン _forward_ にリンクするために、パイプライン _request_ の最後でlinkフィルターをどう使っているか注意してください。これについては後述します。

> ソースコードにおいて、挿入されたパイプライン _request_ は、ポートパイプラインとパイプライン _forward_ の間に置く必要はありません。これはスクリプトの最後に置いても正しく動きます。パイプラインは、ソースパイプライン内の[_joint filter_](#joint-filters) からターゲットのパイプライン名を参照することで互いにリンクします。定義の順番は関係ありません。

### フィルター：connect

ここまで、変数 `_target`に取り組んできました。しかし、まだ固定のターゲットである _localhost:8080_ としか接続できていないので、これを`_target`に変更する必要があります。

``` js
  .pipeline('connection')
-   .connect('localhost:8080')
+   .connect(_target) // WRONG!!!
```

しかし、このコードを実行しても、エラーが返されます。

```
[error] [pjs] Line 35:  .connect(_target)
[error] [pjs]                    ^
[error] [pjs] Error: unresolved identifier
[error] [pjs] Backtrace:
[error]     In (root) at line 35 column 12
```

これは、カスタムグローバル変数が常に何らかのパイプラインに付随しているからです。つまり、パイプラインがライブになっている時だけ、これらの変数もライブになります（グローバル変数の詳細は _Concepts_ の [Context](/intro/concepts#コンテキスト) を参照してください）。もし、着信接続がなければパイプラインもないので、これらの変数もまだ存在しないことになります。

では、これを回避するにはどうすればいいでしょうか？　そこで [パート2](/tutorial/02-echo/) で行った、フィルターパラメータを動的にした方法を使います。実行時に動的な値を返す関数に変えればいいのです。

``` js
  .pipeline('connection')
-   .connect(_target) // WRONG!!!
+   .connect(
+     () => _target
+   )
```

こうすれば、関数`() => _target` は _connection_ パイプラインがライブになっていて、入力がある場合にのみ評価されます。この時点で、`_target`は確実に存在するので、ちゃんと使用できます。

## 動的なパイプラインの選択

しかし、もしターゲットが見つからない場合はどうなるでしょうか？その場合`_target`は`undefined`になるため、メッセージを渡すことはできません。リクエストを _forward_ 以外の他のパイプラインにつないで、そのパイプラインで _404 Not Found_ を返すことができれば最高ですよね。 _link_ フィルターは、まさにこれが出来るように設計されています。いくつかのパイプラインの中から、選択されたものとリンクします。

### フィルター：link

[_link()_](/reference/api/Configuration/link) がコールされると、1組あるいは複数組のペアのパラメーターを受け取ります。それぞれのペアでは、ターゲットとなるパイプラインの名前が最初に来て、その後に、このサブパイプラインを選択すべきかどうかを決定するコールバック関数が続きます。最後のペアは、それが最終のデフォルトを選択する場合には、そのコールバック関数を省略できます。

これによって、いくつか違う使い方ができるようになります。

``` js
// unconditionally link to 'target1'
.link('target1')

// link to 'target1' when _found is truthy
.link('target1', () => _found)

// link to 'target1' when _found is truthy, otherwise 'target2'
.link('target1', () => _found, 'target2')

// link to 'target1' when _found is 1, 'target2' when _found is 2, otherwise 'target3'
.link('target1', () => _found === 1,
      'target2', () => _found === 2,
      'target3')
```

それと共に、 _404_ という名前の別のサブパイプラインを配置します。最初にlinkフィルターに接続し、後で定義します。

``` js
  .pipeline('request')
    .handleMessageStart(
      msg => (
        _target = _router.find(
          msg.head.headers.host,
          msg.head.path,
        )
      )
    )
-   .link('forward')
+   .link(
+     'forward', () => Boolean(_target),
+     '404'
+   )
```

> linkは、真を _yes_ 、偽を _no_ として返すので、 _forward_ の条件付きコールバック関数は、`() => _target`のように単純化することができます。但し、ここではわかりやすくするために、`_target`を明示的にブール値（boolean value）に変換しています。

### フィルター：replaceMessage

パズルの最後のピースは、 _悪名高い404 ページ_ です。上で述べたように、これは _404_ という名前の新しいサブパイプラインで処理します。

[パート1](/tutorial/01-hello/)の _serveHTTP_ と同じ方法でこれができるでしょうか？ 残念ながら答えはノーです。 _serveHTTP_ は、その入力がローバイトのストリームであることを前提とし、つまりHTTPメッセージから「デフレーミング」するという仕事しかしません。この仕事は、ポートパイプライン内で既に _demuxHTTP_ フィルターによって行われています。バイトストリームのデフレーミングを2度することはできません。

新しい _404_ パイプラインに送られるストリームは、すでに「バイトストリーム」から「メッセージストリーム」に変換されているため、入って来るメッセージが固定の _404_ レスポンスメッセージを「リプレース」するのは簡単です。

``` js
  .pipeline('forward')
    .muxHTTP('connection')

  .pipeline('connection')
    .connect(
      () => _target
    )

+ .pipeline('404')
+   .replaceMessage(
+     new Message({ status: 404 }, 'No route')
+   )
```

リクエストが _404_ レスポンスメッセージにリプレースされた後、パイプライン _404_ の末尾に到達することに注意してください。次にどこに行くのか不思議に思うかもしれません。最終的にどのようにクライアント側に到達するでしょうか？

### ジョイントフィルター

Pipyのパイプラインは一方通行です。データは最初のフィルターから入り、最後のフィルターから出ます。ポートパイプラインの場合、クライアントからのリクエストがその入力であり、クライアントへのレスポンスがその出力です。

それがサブパイプラインへ「リンク」するというとき、それはポートパイプラインとサブパイプラインが次から次へと連結していくという意味ではありません。正しくは、そのサブパイプラインをポートパイプライン内に「挿入」するということです。この挿入は、今まで見てきた _link_ 、 _demuxHTTP_ や _muxHTTP_ のような、さまざまなジョイントフィルターの一つを使って行われます。

つまり、リクエストが _404_ ルートを通るとき、その移動経路の全体像は次のようになります。

<div style="text-align: center">
  <SvgRoutingPipelines/>
</div>

この「パイプラインの挿入」は、ジョイントフィルターの場所にサブパイプラインを挟み込み、そのフィルターを完全にリプレースするとイメージしてもらうとよいでしょう。

パイプラインとフィルターについて詳しくは、 _Concept_ の [Filter and pipeline](/intro/concepts/#フィルターとパイプライン) を参照してくだざい。

## 接続の共有問題

さて、このコードをテストしてみると、ルーティングは必ず起こるわけではないことが分かります。何か _粘着_ しているような感じがします。

``` sh
$ curl localhost:8000/hi
Hi, there!
$ curl localhost:8000/ip
Hi, there!

## Try again after 10 seconds...

$ curl localhost:8000/ip
You are requesting /ip from ::ffff:127.0.0.1
$ curl localhost:8000/hi
You are requesting /hi from ::ffff:127.0.0.1
```

これは、アップストリームへの接続が、「_connection_」サブパイプライン内の _connect_ フィルターで確立され、そのパイプラインが _muxHTTP_ フィルターから作られているためです。 _muxHTTP_ フィルターは作成を一度だけ行い、すべてのリクエスト間で同じパイプラインを共有することで、同じアップストリーム接続を介するすべてのリクエストを効果的に転送しています。

それぞれのリクエストが異なる`_target`値を持つ場合でも、変数が使用されるのは接続が確立されるときの1度だけです。それから10秒間のアイドル時間が過ぎるまでは`_target`の値は関連性がなくなり、その後「_connection_」サブパイプラインが閉じられて新しいサブパイプラインが作成され、 _connect_ フィルターは新しい`_target`値を取得して正しいターゲットホストに接続します。

異なるターゲットへのリクエストに異なる「_connection_」サブパイプラインを使用するには、 _muxHTTP_ の2番目のパラメーターで、異なるターゲットの異なる値を指定する必要があります。最も簡単なのは、単純に`_target`の値を使うことです。 _muxHTTP_ は、2番目のパラメーターに異なる値が来るたびに、新しいサブパイプラインを作成します。`_target`に現在の値を渡すと、アップストリーム接続は同じターゲットホストに送信するリクエストの間でのみ共有されます。

``` js
  .pipeline('forward')
-   .muxHTTP('connection')
+   .muxHTTP(
+     'connection',
+     () => _target
+   )

  .pipeline('connection')
    .connect(
      () => _target
    )
```

## テストしてみる

では、このプログラムを動かしてテストをしてみます。

``` sh
$ curl localhost:8000/hi
Hi, there!
$ curl localhost:8000/ip
You are requesting /ip from ::ffff:127.0.0.1
$ curl localhost:8000/echo -d 'hello'
hello
$ curl localhost:8000/bad/path
No route
```

見事に動きました！

## まとめ

このパートでは、簡単なルーティングプロキシの実装方法を解説しました。また、カスタムグローバル変数の定義と使い方、そして _link_ や _handleMessageStart_ や _replaceMessage_ のようなフィルターについても見てきました。

### 要点

1.	[_algo.URLRouter_](/reference/api/algo/URLRouter)を使用して、URLのような文字列をルーティングテーブルに従ってあらゆる種類の値にマップします。これに従えば、単純なルーティングプロキシを実装できます。

2.	_link_ フィルターを使用すれば、リンクが存在するパイプラインにサブパイプラインを選択的に「_inserted_」できます。プログラミング言語の _if_ 文や _switch_ 文のように、制御フローを条件分岐させることができます。

3.	_demuxHTTP_ フィルターにサブパイプラインがある場合は、 _replaceMessage_ を使用して、入力のリクエストメッセージを出力のレスポンスメッセージに変換します。 _demuxHTTP_ と _replaceMessage_ を組み合わせると、 _serveHTTP_ と同じように使える場合もあります。

### 次のパートの内容

このパートから、プロキシのコードロジックはますます長くなり、複雑になっています。その上に、たった一つのスクリプトファイルの中に、どんどん機能を追加するのは良い考えではありません。さらに先に進むには、何らかの「モジュール方式」が必要になります。そこで、次のパートでは「プラグインシステム」の構築を解説します。

